﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Processing.Structures;
using System.Xml;

namespace RayTracer.Environment.Materials
{
    /// <summary>
    /// Hält Materialdaten
    /// </summary>
    [Serializable]
    public struct Material
    {
        #region Eigenschaften
        /// <summary>
        /// Gibt und setzt den ARGB-Ambient-Farbwert des Materials. Der Alphawert wird vom Shadingmodell nicht verwendet
        /// </summary>
        public Color Ambient;

        /// <summary>
        /// Gibt und setzt den ARGB-Diffuse-Farbwert des Materials. Der Alphawert definiert wie die diffuse und die ambiente Farbe in die Farbe am Schnittpunkt miteinberechnet wird. 
        /// </summary>
        public Color Diffuse;

        /// <summary>
        /// Gibt und setzt den ARGB-Specular-Farbwert des Materials. Der Alphawert wird vom Shadingmodell nicht verwendet.
        /// </summary>
        public Color Specular;

        /// <summary>
        /// Gibt und setzt den Reflexionshighlight-Koeffizienten für die spiegelnde Reflexion von Lichtquellen im Beleuchtungsmodell. Dieser Koeffizient bestimmt die größe des Highlights.
        /// </summary>
        public float Shininess;

        /// <summary>
        /// Gibt und setzt den Faktor der gewichtet, wie die Farbe eines reflektierten Strahls in die Farbe am Schnittpunkt miteinberechnet wird.
        /// </summary>
        public float SpecularReflectionFactor;

        /// <summary>
        /// Gibt und setzt den Brechungsindex des Materials. Definiert im Snelliusschen Brechungsgesetz die Richtungsänderung eines Lichtstrahls beim Durchdringen des Materials.
        /// </summary>
        public float RefractiveIndex;

        /// <summary>
        /// Gibt und setzt den Faktor der gewichtet, wie die Farbe eines refraktierten Strahls in die Farbe am Schnittpunkt miteinberechnet wird.  Kann einen Wert zwischen 0.0f und 1.0f annehmen.
        /// </summary>
        public float SpecularTransmissionFactor;

        /// <summary>
        /// Gibt das Standardmaterial an.
        /// </summary>
        public static Material Default { get { return new Material(Color.FromArgb(255, 64, 64, 64), Color.FromArgb(255, 128, 128, 128), Color.FromArgb(255, 192, 192, 192), 1.0f, 1.0f, 1.0f, 0.9f); } }
        #endregion Eigenschaften

        #region Methoden
        /// <summary>
        /// Erstellt ein Material.
        /// </summary>
        /// <param name="ambient">Ambiente Farbe des Materials.</param>
        /// <param name="diffuse">Diffuse Farbe des Materials.</param>
        /// <param name="specular">Spekulare Farbe des Materials.</param>
        /// <param name="shininess">Intensität der Spiegelung.</param>
        /// <param name="specularReflectionFactor">Faktor der gewichtet, wie die Farbe eines reflektierten Strahls in die Farbe am Schnittpunkt miteinberechnet wird. </param>
        /// <param name="refractiveIndex">Brechungsindex, der die Richtungsänderung eines Lichtstrahls beim Durchdingen des Materials angibt.</param>
        /// <param name="specularTransmissionFactor">Faktor der gewichtet, wie die Farbe eines refraktierten Strahls in die Farbe am Schnittpunkt miteinberechnet wird. </param>
        public Material(Color ambient, Color diffuse, Color specular, float shininess, float specularReflectionFactor, float refractiveIndex, float specularTransmissionFactor)
        {
            this.Ambient = ambient;
            this.Diffuse = diffuse;
            this.Specular = specular;
            this.Shininess = shininess;
            this.SpecularReflectionFactor = specularReflectionFactor;
            this.RefractiveIndex = refractiveIndex;
            this.SpecularTransmissionFactor = specularTransmissionFactor;
        }

        #region Typenumwandlung
        /// <summary>
        /// Gibt eine serialisierte Form der Struktur zurück. Übergibt die RGBA Farbe als BGRA 
        /// </summary>
        /// <returns>Die Float16 Struktur.</returns>
        public Float16 Serialize()
        {
            return new Float16(Ambient.B / (float)255, Ambient.G / (float)255, Ambient.R / (float)255, Ambient.A / (float)255,
                               Diffuse.B / (float)255, Diffuse.G / (float)255, Diffuse.R / (float)255, Diffuse.A / (float)255,
                               Specular.B / (float)255, Specular.G / (float)255, Specular.R / (float)255, Specular.A / (float)255,
                               Shininess, SpecularReflectionFactor, RefractiveIndex, SpecularTransmissionFactor);
        }


        /// <summary>
        /// Erstellt einen Vektor aus einem XML-Knoten.
        /// </summary>
        /// <param name="xmlNode">Der XML-Knoten.</param>
        /// <returns>Der Vektor.</returns>
        internal static Material FromXml(XmlNode xmlNode)
        {
            XmlNode nodeAmbient = xmlNode.SelectSingleNode("Ambient");
            XmlNode nodeDiffuse = xmlNode.SelectSingleNode("Diffuse");
            XmlNode nodeSpecular = xmlNode.SelectSingleNode("Specular");
            XmlNode nodeSpecularity = xmlNode.SelectSingleNode("Shininess");
            XmlNode nodeSpecularReflectionFactor = xmlNode.SelectSingleNode("SpecularReflectionFactor");
            XmlNode nodeRefractiveIndex = xmlNode.SelectSingleNode("RefractiveIndex");
            XmlNode nodeSpecularTransmissionFactor = xmlNode.SelectSingleNode("SpecularTransmissionFactor");

            Material m = new Material();
            if (nodeSpecularity != null) float.TryParse(nodeSpecularity.InnerText, out m.Shininess);
            if (nodeSpecularReflectionFactor != null) float.TryParse(nodeSpecularReflectionFactor.InnerText, out m.SpecularReflectionFactor);
            if (nodeRefractiveIndex != null) float.TryParse(nodeRefractiveIndex.InnerText, out m.RefractiveIndex);
            if (nodeSpecularTransmissionFactor != null) float.TryParse(nodeSpecularTransmissionFactor.InnerText, out m.SpecularTransmissionFactor);
            if (nodeAmbient != null) m.Ambient = NetXmlExtensions.ColorFromXml(nodeAmbient);
            if (nodeDiffuse != null) m.Diffuse = NetXmlExtensions.ColorFromXml(nodeDiffuse);
            if (nodeSpecular != null) m.Specular = NetXmlExtensions.ColorFromXml(nodeSpecular);
            
            return m;
        }

        /// <summary>
        /// Wandelt das Material in einen XML-Knoten.
        /// </summary>
        /// <param name="xmlDocument">Das zugrunde liegende XML-Dokument.</param>
        /// <returns>Der XML-Knoten.</returns>
        internal XmlNode ToXml(XmlDocument xmlDocument)
        {
            XmlNode node = xmlDocument.CreateNode(XmlNodeType.Element, typeof(Material).Name, null);
            node.AppendChild(Ambient.ToXml(xmlDocument, "Ambient"));
            node.AppendChild(Diffuse.ToXml(xmlDocument, "Diffuse"));
            node.AppendChild(Specular.ToXml(xmlDocument, "Specular"));

            XmlNode nodeSpecularity = xmlDocument.CreateNode(XmlNodeType.Element, "Shininess", null);
            nodeSpecularity.InnerText = Shininess.ToString();
            XmlNode nodeReflectionFactor = xmlDocument.CreateNode(XmlNodeType.Element, "SpecularReflectionFactor", null);
            nodeReflectionFactor.InnerText = SpecularReflectionFactor.ToString();
            XmlNode nodeRefractiveIndex = xmlDocument.CreateNode(XmlNodeType.Element, "RefractiveIndex", null);
            nodeRefractiveIndex.InnerText = RefractiveIndex.ToString();
            XmlNode nodeRefractionFactor = xmlDocument.CreateNode(XmlNodeType.Element, "SpecularTransmissionFactor", null);
            nodeRefractionFactor.InnerText = SpecularTransmissionFactor.ToString();

            node.AppendChild(nodeSpecularity);
            node.AppendChild(nodeReflectionFactor);
            node.AppendChild(nodeRefractiveIndex);
            node.AppendChild(nodeRefractionFactor);
            return node;
        }
        #endregion Typenumwandlung
        #endregion Methoden
    }
}
